// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package com.google.protobuf;

import static com.google.protobuf.MessageSchema.getMutableUnknownFields;

import com.google.protobuf.Internal.ProtobufList;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * Helper functions to decode protobuf wire format from a byte array.
 *
 * <p>Note that these functions don't do boundary check on the byte array but instead rely on Java
 * VM to check it. That means parsing routines utilizing these functions must catch
 * IndexOutOfBoundsException and convert it to protobuf's InvalidProtocolBufferException when
 * crossing protobuf public API boundaries.
 */
final class ArrayDecoders {
    /**
     * A helper used to return multiple values in a Java function. Java doesn't natively support
     * returning multiple values in a function. Creating a new Object to hold the return values will
     * be too expensive. Instead, we pass a Registers instance to functions that want to return
     * multiple values and let the function set the return value in this Registers instance instead.
     *
     * <p>TODO(xiaofeng): This could be merged into CodedInputStream or CodedInputStreamReader which
     * is already being passed through all the parsing routines.
     */
    static final class Registers {
        public int int1;
        public long long1;
        public Object object1;
        public final ExtensionRegistryLite extensionRegistry;

        Registers() {
            this.extensionRegistry = ExtensionRegistryLite.getEmptyRegistry();
        }

        Registers(ExtensionRegistryLite extensionRegistry) {
            if (extensionRegistry == null) {
                throw new NullPointerException();
            }
            this.extensionRegistry = extensionRegistry;
        }
    }

    /**
     * Decodes a varint. Returns the position after the varint. The decoded varint is stored in
     * registers.int1.
     */
    static int decodeVarint32(byte[] data, int position, Registers registers) {
        int value = data[position++];
        if (value >= 0) {
            registers.int1 = value;
            return position;
        }
        return decodeVarint32(value, data, position, registers);
    }

    /**
     * Like decodeVarint32 except that the first byte is already read.
     */
    static int decodeVarint32(int firstByte, byte[] data, int position, Registers registers) {
        int value = firstByte & 0x7F;
        final byte b2 = data[position++];
        if (b2 >= 0) {
            registers.int1 = value | ((int) b2 << 7);
            return position;
        }
        value |= (b2 & 0x7F) << 7;

        final byte b3 = data[position++];
        if (b3 >= 0) {
            registers.int1 = value | ((int) b3 << 14);
            return position;
        }
        value |= (b3 & 0x7F) << 14;

        final byte b4 = data[position++];
        if (b4 >= 0) {
            registers.int1 = value | ((int) b4 << 21);
            return position;
        }
        value |= (b4 & 0x7F) << 21;

        final byte b5 = data[position++];
        if (b5 >= 0) {
            registers.int1 = value | ((int) b5 << 28);
            return position;
        }
        value |= (b5 & 0x7F) << 28;

        while (data[position++] < 0) {
        }

        registers.int1 = value;
        return position;
    }

    /**
     * Decodes a varint. Returns the position after the varint. The decoded varint is stored in
     * registers.long1.
     */
    static int decodeVarint64(byte[] data, int position, Registers registers) {
        long value = data[position++];
        if (value >= 0) {
            registers.long1 = value;
            return position;
        } else {
            return decodeVarint64(value, data, position, registers);
        }
    }

    /**
     * Like decodeVarint64 except that the first byte is already read.
     */
    static int decodeVarint64(long firstByte, byte[] data, int position, Registers registers) {
        long value = firstByte & 0x7F;
        byte next = data[position++];
        int shift = 7;
        value |= (long) (next & 0x7F) << 7;
        while (next < 0) {
            next = data[position++];
            shift += 7;
            value |= (long) (next & 0x7F) << shift;
        }
        registers.long1 = value;
        return position;
    }

    /**
     * Decodes and returns a fixed32 value.
     */
    static int decodeFixed32(byte[] data, int position) {
        return (data[position] & 0xff)
                | ((data[position + 1] & 0xff) << 8)
                | ((data[position + 2] & 0xff) << 16)
                | ((data[position + 3] & 0xff) << 24);
    }

    /**
     * Decodes and returns a fixed64 value.
     */
    static long decodeFixed64(byte[] data, int position) {
        return (data[position] & 0xffL)
                | ((data[position + 1] & 0xffL) << 8)
                | ((data[position + 2] & 0xffL) << 16)
                | ((data[position + 3] & 0xffL) << 24)
                | ((data[position + 4] & 0xffL) << 32)
                | ((data[position + 5] & 0xffL) << 40)
                | ((data[position + 6] & 0xffL) << 48)
                | ((data[position + 7] & 0xffL) << 56);
    }

    /**
     * Decodes and returns a double value.
     */
    static double decodeDouble(byte[] data, int position) {
        return Double.longBitsToDouble(decodeFixed64(data, position));
    }

    /**
     * Decodes and returns a float value.
     */
    static float decodeFloat(byte[] data, int position) {
        return Float.intBitsToFloat(decodeFixed32(data, position));
    }

    /**
     * Decodes a string value.
     */
    static int decodeString(byte[] data, int position, Registers registers)
            throws InvalidProtocolBufferException {
        position = decodeVarint32(data, position, registers);
        final int length = registers.int1;
        if (length < 0) {
            throw InvalidProtocolBufferException.negativeSize();
        } else if (length == 0) {
            registers.object1 = "";
            return position;
        } else {
            registers.object1 = new String(data, position, length, StandardCharsets.UTF_8);
            return position + length;
        }
    }

    /**
     * Decodes a string value with utf8 check.
     */
    static int decodeStringRequireUtf8(byte[] data, int position, Registers registers)
            throws InvalidProtocolBufferException {
        position = decodeVarint32(data, position, registers);
        final int length = registers.int1;
        if (length < 0) {
            throw InvalidProtocolBufferException.negativeSize();
        } else if (length == 0) {
            registers.object1 = "";
            return position;
        } else {
            registers.object1 = Utf8.decodeUtf8(data, position, length);
            return position + length;
        }
    }

    /**
     * Decodes a bytes value.
     */
    static int decodeBytes(byte[] data, int position, Registers registers)
            throws InvalidProtocolBufferException {
        position = decodeVarint32(data, position, registers);
        final int length = registers.int1;
        if (length < 0) {
            throw InvalidProtocolBufferException.negativeSize();
        } else if (length > data.length - position) {
            throw InvalidProtocolBufferException.truncatedMessage();
        } else if (length == 0) {
            registers.object1 = ByteString.EMPTY;
            return position;
        } else {
            registers.object1 = ByteString.copyFrom(data, position, length);
            return position + length;
        }
    }

    /**
     * Decodes a message value.
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    static int decodeMessageField(
            Schema schema, byte[] data, int position, int limit, Registers registers) throws IOException {
        int length = data[position++];
        if (length < 0) {
            position = decodeVarint32(length, data, position, registers);
            length = registers.int1;
        }
        if (length < 0 || length > limit - position) {
            throw InvalidProtocolBufferException.truncatedMessage();
        }
        Object result = schema.newInstance();
        schema.mergeFrom(result, data, position, position + length, registers);
        schema.makeImmutable(result);
        registers.object1 = result;
        return position + length;
    }

    /**
     * Decodes a group value.
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    static int decodeGroupField(
            Schema schema, byte[] data, int position, int limit, int endGroup, Registers registers)
            throws IOException {
        // A group field must has a MessageSchema (the only other subclass of Schema is MessageSetSchema
        // and it can't be used in group fields).
        final MessageSchema messageSchema = (MessageSchema) schema;
        Object result = messageSchema.newInstance();
        // It's OK to directly use parseProto2Message since proto3 doesn't have group.
        final int endPosition =
                messageSchema.parseProto2Message(result, data, position, limit, endGroup, registers);
        messageSchema.makeImmutable(result);
        registers.object1 = result;
        return endPosition;
    }

    /**
     * Decodes a repeated 32-bit varint field. Returns the position after all read values.
     */
    static int decodeVarint32List(
            int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers) {
        final IntArrayList output = (IntArrayList) list;
        position = decodeVarint32(data, position, registers);
        output.addInt(registers.int1);
        while (position < limit) {
            int nextPosition = decodeVarint32(data, position, registers);
            if (tag != registers.int1) {
                break;
            }
            position = decodeVarint32(data, nextPosition, registers);
            output.addInt(registers.int1);
        }
        return position;
    }

    /**
     * Decodes a repeated 64-bit varint field. Returns the position after all read values.
     */
    static int decodeVarint64List(
            int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers) {
        final LongArrayList output = (LongArrayList) list;
        position = decodeVarint64(data, position, registers);
        output.addLong(registers.long1);
        while (position < limit) {
            int nextPosition = decodeVarint32(data, position, registers);
            if (tag != registers.int1) {
                break;
            }
            position = decodeVarint64(data, nextPosition, registers);
            output.addLong(registers.long1);
        }
        return position;
    }

    /**
     * Decodes a repeated fixed32 field. Returns the position after all read values.
     */
    static int decodeFixed32List(
            int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers) {
        final IntArrayList output = (IntArrayList) list;
        output.addInt(decodeFixed32(data, position));
        position += 4;
        while (position < limit) {
            int nextPosition = decodeVarint32(data, position, registers);
            if (tag != registers.int1) {
                break;
            }
            output.addInt(decodeFixed32(data, nextPosition));
            position = nextPosition + 4;
        }
        return position;
    }

    /**
     * Decodes a repeated fixed64 field. Returns the position after all read values.
     */
    static int decodeFixed64List(
            int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers) {
        final LongArrayList output = (LongArrayList) list;
        output.addLong(decodeFixed64(data, position));
        position += 8;
        while (position < limit) {
            int nextPosition = decodeVarint32(data, position, registers);
            if (tag != registers.int1) {
                break;
            }
            output.addLong(decodeFixed64(data, nextPosition));
            position = nextPosition + 8;
        }
        return position;
    }

    /**
     * Decodes a repeated float field. Returns the position after all read values.
     */
    static int decodeFloatList(
            int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers) {
        final FloatArrayList output = (FloatArrayList) list;
        output.addFloat(decodeFloat(data, position));
        position += 4;
        while (position < limit) {
            int nextPosition = decodeVarint32(data, position, registers);
            if (tag != registers.int1) {
                break;
            }
            output.addFloat(decodeFloat(data, nextPosition));
            position = nextPosition + 4;
        }
        return position;
    }

    /**
     * Decodes a repeated double field. Returns the position after all read values.
     */
    static int decodeDoubleList(
            int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers) {
        final DoubleArrayList output = (DoubleArrayList) list;
        output.addDouble(decodeDouble(data, position));
        position += 8;
        while (position < limit) {
            int nextPosition = decodeVarint32(data, position, registers);
            if (tag != registers.int1) {
                break;
            }
            output.addDouble(decodeDouble(data, nextPosition));
            position = nextPosition + 8;
        }
        return position;
    }

    /**
     * Decodes a repeated boolean field. Returns the position after all read values.
     */
    static int decodeBoolList(
            int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers) {
        final BooleanArrayList output = (BooleanArrayList) list;
        position = decodeVarint64(data, position, registers);
        output.addBoolean(registers.long1 != 0);
        while (position < limit) {
            int nextPosition = decodeVarint32(data, position, registers);
            if (tag != registers.int1) {
                break;
            }
            position = decodeVarint64(data, nextPosition, registers);
            output.addBoolean(registers.long1 != 0);
        }
        return position;
    }

    /**
     * Decodes a repeated sint32 field. Returns the position after all read values.
     */
    static int decodeSInt32List(
            int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers) {
        final IntArrayList output = (IntArrayList) list;
        position = decodeVarint32(data, position, registers);
        output.addInt(CodedInputStream.decodeZigZag32(registers.int1));
        while (position < limit) {
            int nextPosition = decodeVarint32(data, position, registers);
            if (tag != registers.int1) {
                break;
            }
            position = decodeVarint32(data, nextPosition, registers);
            output.addInt(CodedInputStream.decodeZigZag32(registers.int1));
        }
        return position;
    }

    /**
     * Decodes a repeated sint64 field. Returns the position after all read values.
     */
    static int decodeSInt64List(
            int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers) {
        final LongArrayList output = (LongArrayList) list;
        position = decodeVarint64(data, position, registers);
        output.addLong(CodedInputStream.decodeZigZag64(registers.long1));
        while (position < limit) {
            int nextPosition = decodeVarint32(data, position, registers);
            if (tag != registers.int1) {
                break;
            }
            position = decodeVarint64(data, nextPosition, registers);
            output.addLong(CodedInputStream.decodeZigZag64(registers.long1));
        }
        return position;
    }

    /**
     * Decodes a packed 32-bit varint field. Returns the position after all read values.
     */
    static int decodePackedVarint32List(
            byte[] data, int position, ProtobufList<?> list, Registers registers) throws IOException {
        final IntArrayList output = (IntArrayList) list;
        position = decodeVarint32(data, position, registers);
        final int fieldLimit = position + registers.int1;
        while (position < fieldLimit) {
            position = decodeVarint32(data, position, registers);
            output.addInt(registers.int1);
        }
        if (position != fieldLimit) {
            throw InvalidProtocolBufferException.truncatedMessage();
        }
        return position;
    }

    /**
     * Decodes a packed 64-bit varint field. Returns the position after all read values.
     */
    static int decodePackedVarint64List(
            byte[] data, int position, ProtobufList<?> list, Registers registers) throws IOException {
        final LongArrayList output = (LongArrayList) list;
        position = decodeVarint32(data, position, registers);
        final int fieldLimit = position + registers.int1;
        while (position < fieldLimit) {
            position = decodeVarint64(data, position, registers);
            output.addLong(registers.long1);
        }
        if (position != fieldLimit) {
            throw InvalidProtocolBufferException.truncatedMessage();
        }
        return position;
    }

    /**
     * Decodes a packed fixed32 field. Returns the position after all read values.
     */
    static int decodePackedFixed32List(
            byte[] data, int position, ProtobufList<?> list, Registers registers) throws IOException {
        final IntArrayList output = (IntArrayList) list;
        position = decodeVarint32(data, position, registers);
        final int fieldLimit = position + registers.int1;
        while (position < fieldLimit) {
            output.addInt(decodeFixed32(data, position));
            position += 4;
        }
        if (position != fieldLimit) {
            throw InvalidProtocolBufferException.truncatedMessage();
        }
        return position;
    }

    /**
     * Decodes a packed fixed64 field. Returns the position after all read values.
     */
    static int decodePackedFixed64List(
            byte[] data, int position, ProtobufList<?> list, Registers registers) throws IOException {
        final LongArrayList output = (LongArrayList) list;
        position = decodeVarint32(data, position, registers);
        final int fieldLimit = position + registers.int1;
        while (position < fieldLimit) {
            output.addLong(decodeFixed64(data, position));
            position += 8;
        }
        if (position != fieldLimit) {
            throw InvalidProtocolBufferException.truncatedMessage();
        }
        return position;
    }

    /**
     * Decodes a packed float field. Returns the position after all read values.
     */
    static int decodePackedFloatList(
            byte[] data, int position, ProtobufList<?> list, Registers registers) throws IOException {
        final FloatArrayList output = (FloatArrayList) list;
        position = decodeVarint32(data, position, registers);
        final int fieldLimit = position + registers.int1;
        while (position < fieldLimit) {
            output.addFloat(decodeFloat(data, position));
            position += 4;
        }
        if (position != fieldLimit) {
            throw InvalidProtocolBufferException.truncatedMessage();
        }
        return position;
    }

    /**
     * Decodes a packed double field. Returns the position after all read values.
     */
    static int decodePackedDoubleList(
            byte[] data, int position, ProtobufList<?> list, Registers registers) throws IOException {
        final DoubleArrayList output = (DoubleArrayList) list;
        position = decodeVarint32(data, position, registers);
        final int fieldLimit = position + registers.int1;
        while (position < fieldLimit) {
            output.addDouble(decodeDouble(data, position));
            position += 8;
        }
        if (position != fieldLimit) {
            throw InvalidProtocolBufferException.truncatedMessage();
        }
        return position;
    }

    /**
     * Decodes a packed boolean field. Returns the position after all read values.
     */
    static int decodePackedBoolList(
            byte[] data, int position, ProtobufList<?> list, Registers registers) throws IOException {
        final BooleanArrayList output = (BooleanArrayList) list;
        position = decodeVarint32(data, position, registers);
        final int fieldLimit = position + registers.int1;
        while (position < fieldLimit) {
            position = decodeVarint64(data, position, registers);
            output.addBoolean(registers.long1 != 0);
        }
        if (position != fieldLimit) {
            throw InvalidProtocolBufferException.truncatedMessage();
        }
        return position;
    }

    /**
     * Decodes a packed sint32 field. Returns the position after all read values.
     */
    static int decodePackedSInt32List(
            byte[] data, int position, ProtobufList<?> list, Registers registers) throws IOException {
        final IntArrayList output = (IntArrayList) list;
        position = decodeVarint32(data, position, registers);
        final int fieldLimit = position + registers.int1;
        while (position < fieldLimit) {
            position = decodeVarint32(data, position, registers);
            output.addInt(CodedInputStream.decodeZigZag32(registers.int1));
        }
        if (position != fieldLimit) {
            throw InvalidProtocolBufferException.truncatedMessage();
        }
        return position;
    }

    /**
     * Decodes a packed sint64 field. Returns the position after all read values.
     */
    @SuppressWarnings("unchecked")
    static int decodePackedSInt64List(
            byte[] data, int position, ProtobufList<?> list, Registers registers) throws IOException {
        final LongArrayList output = (LongArrayList) list;
        position = decodeVarint32(data, position, registers);
        final int fieldLimit = position + registers.int1;
        while (position < fieldLimit) {
            position = decodeVarint64(data, position, registers);
            output.addLong(CodedInputStream.decodeZigZag64(registers.long1));
        }
        if (position != fieldLimit) {
            throw InvalidProtocolBufferException.truncatedMessage();
        }
        return position;
    }

    /**
     * Decodes a repeated string field. Returns the position after all read values.
     */
    @SuppressWarnings("unchecked")
    static int decodeStringList(
            int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers)
            throws InvalidProtocolBufferException {
        final ProtobufList<String> output = (ProtobufList<String>) list;
        position = decodeVarint32(data, position, registers);
        final int length = registers.int1;
        if (length < 0) {
            throw InvalidProtocolBufferException.negativeSize();
        } else if (length == 0) {
            output.add("");
        } else {
            String value = new String(data, position, length, StandardCharsets.UTF_8);
            output.add(value);
            position += length;
        }
        while (position < limit) {
            int nextPosition = decodeVarint32(data, position, registers);
            if (tag != registers.int1) {
                break;
            }
            position = decodeVarint32(data, nextPosition, registers);
            final int nextLength = registers.int1;
            if (nextLength < 0) {
                throw InvalidProtocolBufferException.negativeSize();
            } else if (nextLength == 0) {
                output.add("");
            } else {
                String value = new String(data, position, nextLength, StandardCharsets.UTF_8);
                output.add(value);
                position += nextLength;
            }
        }
        return position;
    }

    /**
     * Decodes a repeated string field with utf8 check. Returns the position after all read values.
     */
    @SuppressWarnings("unchecked")
    static int decodeStringListRequireUtf8(
            int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers)
            throws InvalidProtocolBufferException {
        final ProtobufList<String> output = (ProtobufList<String>) list;
        position = decodeVarint32(data, position, registers);
        final int length = registers.int1;
        if (length < 0) {
            throw InvalidProtocolBufferException.negativeSize();
        } else if (length == 0) {
            output.add("");
        } else {
            if (!Utf8.isValidUtf8(data, position, position + length)) {
                throw InvalidProtocolBufferException.invalidUtf8();
            }
            String value = new String(data, position, length, StandardCharsets.UTF_8);
            output.add(value);
            position += length;
        }
        while (position < limit) {
            int nextPosition = decodeVarint32(data, position, registers);
            if (tag != registers.int1) {
                break;
            }
            position = decodeVarint32(data, nextPosition, registers);
            final int nextLength = registers.int1;
            if (nextLength < 0) {
                throw InvalidProtocolBufferException.negativeSize();
            } else if (nextLength == 0) {
                output.add("");
            } else {
                if (!Utf8.isValidUtf8(data, position, position + nextLength)) {
                    throw InvalidProtocolBufferException.invalidUtf8();
                }
                String value = new String(data, position, nextLength, StandardCharsets.UTF_8);
                output.add(value);
                position += nextLength;
            }
        }
        return position;
    }

    /**
     * Decodes a repeated bytes field. Returns the position after all read values.
     */
    @SuppressWarnings("unchecked")
    static int decodeBytesList(
            int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers)
            throws InvalidProtocolBufferException {
        final ProtobufList<ByteString> output = (ProtobufList<ByteString>) list;
        position = decodeVarint32(data, position, registers);
        final int length = registers.int1;
        if (length < 0) {
            throw InvalidProtocolBufferException.negativeSize();
        } else if (length > data.length - position) {
            throw InvalidProtocolBufferException.truncatedMessage();
        } else if (length == 0) {
            output.add(ByteString.EMPTY);
        } else {
            output.add(ByteString.copyFrom(data, position, length));
            position += length;
        }
        while (position < limit) {
            int nextPosition = decodeVarint32(data, position, registers);
            if (tag != registers.int1) {
                break;
            }
            position = decodeVarint32(data, nextPosition, registers);
            final int nextLength = registers.int1;
            if (nextLength < 0) {
                throw InvalidProtocolBufferException.negativeSize();
            } else if (nextLength > data.length - position) {
                throw InvalidProtocolBufferException.truncatedMessage();
            } else if (nextLength == 0) {
                output.add(ByteString.EMPTY);
            } else {
                output.add(ByteString.copyFrom(data, position, nextLength));
                position += nextLength;
            }
        }
        return position;
    }

    /**
     * Decodes a repeated message field
     *
     * @return The position of after read all messages
     */
    @SuppressWarnings({"unchecked"})
    static int decodeMessageList(
            Schema<?> schema,
            int tag,
            byte[] data,
            int position,
            int limit,
            ProtobufList<?> list,
            Registers registers)
            throws IOException {
        final ProtobufList<Object> output = (ProtobufList<Object>) list;
        position = decodeMessageField(schema, data, position, limit, registers);
        output.add(registers.object1);
        while (position < limit) {
            int nextPosition = decodeVarint32(data, position, registers);
            if (tag != registers.int1) {
                break;
            }
            position = decodeMessageField(schema, data, nextPosition, limit, registers);
            output.add(registers.object1);
        }
        return position;
    }

    /**
     * Decodes a repeated group field
     *
     * @return The position of after read all groups
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    static int decodeGroupList(
            Schema schema,
            int tag,
            byte[] data,
            int position,
            int limit,
            ProtobufList<?> list,
            Registers registers)
            throws IOException {
        final ProtobufList<Object> output = (ProtobufList<Object>) list;
        final int endgroup = (tag & ~0x7) | WireFormat.WIRETYPE_END_GROUP;
        position = decodeGroupField(schema, data, position, limit, endgroup, registers);
        output.add(registers.object1);
        while (position < limit) {
            int nextPosition = decodeVarint32(data, position, registers);
            if (tag != registers.int1) {
                break;
            }
            position = decodeGroupField(schema, data, nextPosition, limit, endgroup, registers);
            output.add(registers.object1);
        }
        return position;
    }

    static int decodeExtensionOrUnknownField(
            int tag, byte[] data, int position, int limit,
            Object message,
            MessageLite defaultInstance,
            UnknownFieldSchema<UnknownFieldSetLite, UnknownFieldSetLite> unknownFieldSchema,
            Registers registers)
            throws IOException {
        final int number = tag >>> 3;
        GeneratedMessageLite.GeneratedExtension extension =
                registers.extensionRegistry.findLiteExtensionByNumber(defaultInstance, number);
        if (extension == null) {
            return decodeUnknownField(
                    tag, data, position, limit, getMutableUnknownFields(message), registers);
        } else {
            ((GeneratedMessageLite.ExtendableMessage<?, ?>) message).ensureExtensionsAreMutable();
            return decodeExtension(
                    tag, data, position, limit, (GeneratedMessageLite.ExtendableMessage) message,
                    extension, unknownFieldSchema, registers);
        }
    }

    static int decodeExtension(
            int tag,
            byte[] data,
            int position,
            int limit,
            GeneratedMessageLite.ExtendableMessage<?, ?> message,
            GeneratedMessageLite.GeneratedExtension<?, ?> extension,
            UnknownFieldSchema<UnknownFieldSetLite, UnknownFieldSetLite> unknownFieldSchema,
            Registers registers)
            throws IOException {
        final FieldSet<GeneratedMessageLite.ExtensionDescriptor> extensions = message.extensions;
        final int fieldNumber = tag >>> 3;
        if (extension.descriptor.isRepeated() && extension.descriptor.isPacked()) {
            switch (extension.getLiteType()) {
                case DOUBLE: {
                    DoubleArrayList list = new DoubleArrayList();
                    position = decodePackedDoubleList(data, position, list, registers);
                    extensions.setField(extension.descriptor, list);
                    break;
                }
                case FLOAT: {
                    FloatArrayList list = new FloatArrayList();
                    position = decodePackedFloatList(data, position, list, registers);
                    extensions.setField(extension.descriptor, list);
                    break;
                }
                case INT64:
                case UINT64: {
                    LongArrayList list = new LongArrayList();
                    position = decodePackedVarint64List(data, position, list, registers);
                    extensions.setField(extension.descriptor, list);
                    break;
                }
                case INT32:
                case UINT32: {
                    IntArrayList list = new IntArrayList();
                    position = decodePackedVarint32List(data, position, list, registers);
                    extensions.setField(extension.descriptor, list);
                    break;
                }
                case FIXED64:
                case SFIXED64: {
                    LongArrayList list = new LongArrayList();
                    position = decodePackedFixed64List(data, position, list, registers);
                    extensions.setField(extension.descriptor, list);
                    break;
                }
                case FIXED32:
                case SFIXED32: {
                    IntArrayList list = new IntArrayList();
                    position = decodePackedFixed32List(data, position, list, registers);
                    extensions.setField(extension.descriptor, list);
                    break;
                }
                case BOOL: {
                    BooleanArrayList list = new BooleanArrayList();
                    position = decodePackedBoolList(data, position, list, registers);
                    extensions.setField(extension.descriptor, list);
                    break;
                }
                case SINT32: {
                    IntArrayList list = new IntArrayList();
                    position = decodePackedSInt32List(data, position, list, registers);
                    extensions.setField(extension.descriptor, list);
                    break;
                }
                case SINT64: {
                    LongArrayList list = new LongArrayList();
                    position = decodePackedSInt64List(data, position, list, registers);
                    extensions.setField(extension.descriptor, list);
                    break;
                }
                case ENUM: {
                    IntArrayList list = new IntArrayList();
                    position = decodePackedVarint32List(data, position, list, registers);
                    UnknownFieldSetLite unknownFields = message.unknownFields;
                    if (unknownFields == UnknownFieldSetLite.getDefaultInstance()) {
                        unknownFields = null;
                    }
                    unknownFields =
                            SchemaUtil.filterUnknownEnumList(
                                    fieldNumber,
                                    list,
                                    extension.descriptor.getEnumType(),
                                    unknownFields,
                                    unknownFieldSchema);
                    if (unknownFields != null) {
                        message.unknownFields = unknownFields;
                    }
                    extensions.setField(extension.descriptor, list);
                    break;
                }
                default:
                    throw new IllegalStateException(
                            "Type cannot be packed: " + extension.descriptor.getLiteType());
            }
        } else {
            Object value = null;
            // Enum is a special case because unknown enum values will be put into UnknownFieldSetLite.
            if (extension.getLiteType() == WireFormat.FieldType.ENUM) {
                position = decodeVarint32(data, position, registers);
                Object enumValue = extension.descriptor.getEnumType().findValueByNumber(registers.int1);
                if (enumValue == null) {
                    UnknownFieldSetLite unknownFields = ((GeneratedMessageLite) message).unknownFields;
                    if (unknownFields == UnknownFieldSetLite.getDefaultInstance()) {
                        unknownFields = UnknownFieldSetLite.newInstance();
                        ((GeneratedMessageLite) message).unknownFields = unknownFields;
                    }
                    SchemaUtil.storeUnknownEnum(
                            fieldNumber, registers.int1, unknownFields, unknownFieldSchema);
                    return position;
                }
                // Note, we store the integer value instead of the actual enum object in FieldSet.
                // This is also different from full-runtime where we store EnumValueDescriptor.
                value = registers.int1;
            } else {
                switch (extension.getLiteType()) {
                    case DOUBLE:
                        value = decodeDouble(data, position);
                        position += 8;
                        break;
                    case FLOAT:
                        value = decodeFloat(data, position);
                        position += 4;
                        break;
                    case INT64:
                    case UINT64:
                        position = decodeVarint64(data, position, registers);
                        value = registers.long1;
                        break;
                    case INT32:
                    case UINT32:
                        position = decodeVarint32(data, position, registers);
                        value = registers.int1;
                        break;
                    case FIXED64:
                    case SFIXED64:
                        value = decodeFixed64(data, position);
                        position += 8;
                        break;
                    case FIXED32:
                    case SFIXED32:
                        value = decodeFixed32(data, position);
                        position += 4;
                        break;
                    case BOOL:
                        position = decodeVarint64(data, position, registers);
                        value = (registers.long1 != 0);
                        break;
                    case BYTES:
                        position = decodeBytes(data, position, registers);
                        value = registers.object1;
                        break;
                    case SINT32:
                        position = decodeVarint32(data, position, registers);
                        value = CodedInputStream.decodeZigZag32(registers.int1);
                        break;
                    case SINT64:
                        position = decodeVarint64(data, position, registers);
                        value = CodedInputStream.decodeZigZag64(registers.long1);
                        break;
                    case STRING:
                        position = decodeString(data, position, registers);
                        value = registers.object1;
                        break;
                    case GROUP:
                        final int endTag = (fieldNumber << 3) | WireFormat.WIRETYPE_END_GROUP;
                        position = decodeGroupField(
                                Protobuf.getInstance().schemaFor(extension.getMessageDefaultInstance().getClass()),
                                data, position, limit, endTag, registers);
                        value = registers.object1;
                        break;

                    case MESSAGE:
                        position = decodeMessageField(
                                Protobuf.getInstance().schemaFor(extension.getMessageDefaultInstance().getClass()),
                                data, position, limit, registers);
                        value = registers.object1;
                        break;

                    case ENUM:
                        throw new IllegalStateException("Shouldn't reach here.");
                }
            }
            if (extension.isRepeated()) {
                extensions.addRepeatedField(extension.descriptor, value);
            } else {
                switch (extension.getLiteType()) {
                    case MESSAGE:
                    case GROUP:
                        Object oldValue = extensions.getField(extension.descriptor);
                        if (oldValue != null) {
                            value = Internal.mergeMessage(oldValue, value);
                        }
                        break;
                    default:
                        break;
                }
                extensions.setField(extension.descriptor, value);
            }
        }
        return position;
    }

    /**
     * Decodes an unknown field.
     */
    static int decodeUnknownField(
            int tag,
            byte[] data,
            int position,
            int limit,
            UnknownFieldSetLite unknownFields,
            Registers registers)
            throws InvalidProtocolBufferException {
        if (WireFormat.getTagFieldNumber(tag) == 0) {
            throw InvalidProtocolBufferException.invalidTag();
        }
        switch (WireFormat.getTagWireType(tag)) {
            case WireFormat.WIRETYPE_VARINT:
                position = decodeVarint64(data, position, registers);
                unknownFields.storeField(tag, registers.long1);
                return position;
            case WireFormat.WIRETYPE_FIXED32:
                unknownFields.storeField(tag, decodeFixed32(data, position));
                return position + 4;
            case WireFormat.WIRETYPE_FIXED64:
                unknownFields.storeField(tag, decodeFixed64(data, position));
                return position + 8;
            case WireFormat.WIRETYPE_LENGTH_DELIMITED:
                position = decodeVarint32(data, position, registers);
                final int length = registers.int1;
                if (length < 0) {
                    throw InvalidProtocolBufferException.negativeSize();
                } else if (length > data.length - position) {
                    throw InvalidProtocolBufferException.truncatedMessage();
                } else if (length == 0) {
                    unknownFields.storeField(tag, ByteString.EMPTY);
                } else {
                    unknownFields.storeField(tag, ByteString.copyFrom(data, position, length));
                }
                return position + length;
            case WireFormat.WIRETYPE_START_GROUP:
                final UnknownFieldSetLite child = UnknownFieldSetLite.newInstance();
                final int endGroup = (tag & ~0x7) | WireFormat.WIRETYPE_END_GROUP;
                int lastTag = 0;
                while (position < limit) {
                    position = decodeVarint32(data, position, registers);
                    lastTag = registers.int1;
                    if (lastTag == endGroup) {
                        break;
                    }
                    position = decodeUnknownField(lastTag, data, position, limit, child, registers);
                }
                if (position > limit || lastTag != endGroup) {
                    throw InvalidProtocolBufferException.parseFailure();
                }
                unknownFields.storeField(tag, child);
                return position;
            default:
                throw InvalidProtocolBufferException.invalidTag();
        }
    }

    /**
     * Skips an unknown field.
     */
    static int skipField(int tag, byte[] data, int position, int limit, Registers registers)
            throws InvalidProtocolBufferException {
        if (WireFormat.getTagFieldNumber(tag) == 0) {
            throw InvalidProtocolBufferException.invalidTag();
        }
        switch (WireFormat.getTagWireType(tag)) {
            case WireFormat.WIRETYPE_VARINT:
                position = decodeVarint64(data, position, registers);
                return position;
            case WireFormat.WIRETYPE_FIXED32:
                return position + 4;
            case WireFormat.WIRETYPE_FIXED64:
                return position + 8;
            case WireFormat.WIRETYPE_LENGTH_DELIMITED:
                position = decodeVarint32(data, position, registers);
                return position + registers.int1;
            case WireFormat.WIRETYPE_START_GROUP:
                final int endGroup = (tag & ~0x7) | WireFormat.WIRETYPE_END_GROUP;
                int lastTag = 0;
                while (position < limit) {
                    position = decodeVarint32(data, position, registers);
                    lastTag = registers.int1;
                    if (lastTag == endGroup) {
                        break;
                    }
                    position = skipField(lastTag, data, position, limit, registers);
                }
                if (position > limit || lastTag != endGroup) {
                    throw InvalidProtocolBufferException.parseFailure();
                }
                return position;
            default:
                throw InvalidProtocolBufferException.invalidTag();
        }
    }
}
